package com.solidtrust.crawler.service;

import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.annotation.PostConstruct;

import org.apache.commons.io.IOUtils;
import org.apache.log4j.Logger;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.solidtrust.crawler.framework.IUrlOverrider;
import com.solidtrust.crawler.model.PageSource;
import com.solidtrust.dao.CommonDao;
import com.solidtrust.entity.CarCategory;
import com.solidtrust.entity.GanjiCity;
import com.solidtrust.entity.GanjiModel;
import com.solidtrust.entity.ModelSynon;
import com.solidtrust.repository.CarCategoryRepository;
import com.solidtrust.repository.GanjiCityRepository;
import com.solidtrust.repository.GanjiModelRepository;
import com.solidtrust.repository.ModelSynonRepository;

@Service
public class GanjiService {
	private static final Logger log4j = Logger.getLogger(GanjiService.class);
	private static final int TIMEOUT = 1000 * 10;
	private static final String BRAND_MODEL_API_URL = "http://www.ganji.com/ajax.php?dir=vehicle&module=getNewLetterMajorCategory&url=/bmw";
	private static final String HOST = "http://bj.ganji.com";
	private static final String CITY_START_URL = "http://www.ganji.com/ershouche/";
	private static final String SOURCE_NAME = "ganji";
	
	private ObjectMapper om = new ObjectMapper();
	Map<String, List<String>> synonMap;
	Map<String, String> m2_m1;
	
	@Autowired GanjiModelRepository modelRep;
	@Autowired GanjiCityRepository cityRep;
	@Autowired CarCategoryRepository cateRep;
	@Autowired ModelSynonRepository modelSynRep;
	@Autowired CommonDao dao;
	
	@PostConstruct
	public void init(){
		Map<String, List<String>> map = new HashMap<>();
		Map<String, String> m2_m1 = new HashMap<>();
		List<ModelSynon> list = modelSynRep.findAll();
		String last = null;
		for (ModelSynon ms : list) {
			List<String> syns = map.get(ms.getM1());
			if(syns == null){
				syns = new ArrayList<>();
				map.put(ms.getM1(), syns);
				last = ms.getM1();
			}
			
			String m2 = ms.getM2();
			if(last != null){
				for (String s : map.get(last)) {
					if(m2.equals(s)){
						m2_m1.put(m2, last);
					}
				}
			}
			syns.add(m2);
		}
		synonMap = map;
		this.m2_m1 = m2_m1;
		
	}
	
	public Map<String, String> getModelMap(){
		Map<String, String> map = new HashMap<>();
		List<GanjiModel> list = modelRep.findAll();
		log4j.info("ganji model mapping: " + list.size());
		for (GanjiModel m : list) {
			map.put(m.getUrl(), m.getModel());
		}
		return map;
	}
	
	
	public String getExactModel(String adTitle, String model){
		List<String> list = synonMap.get(model);
		if(list != null){
			for (int i = 0; i < list.size(); i++) {
				String m2 = list.get(list.size() - i - 1);
				if(adTitle.contains(m2)){
					String s = m2_m1.get(m2);
					if(s != null){
						list = synonMap.get(s);
						for (String _m2 : list) {
							if(adTitle.contains(_m2)){
								model = _m2;
							}
						}
					}else{
						model = m2;
					}
					break;
				}
			}
		}
		return model;
	}
	
	public List<PageSource> getCities(){
		List<GanjiCity> list = cityRep.findAll();
		List<PageSource> cities = new ArrayList<>(list.size());
		for (GanjiCity g : list) {
			PageSource p = new PageSource();
			p.setUrl(g.getUrl());
			p.setName(g.getCity());
			p.setId(p.getName());
			cities.add(p);
		}
		return cities;
	}
	
	//used for scraping
	public void crawlCategory()throws IOException{
		long l = cateRep.countBySource(SOURCE_NAME);
		log4j.info(SOURCE_NAME + " car category: " + l);
		if(l == 0){
			List<CarCategory> categories = new ArrayList<>();
			List<PageSource> brands = getBrands();
			List<PageSource> cities = getCities();
			for (PageSource b : brands) {
				for (PageSource c : cities) {
					CarCategory mc = new CarCategory();
					mc.setBrand(b.getName());
					mc.setCity(c.getName());
					mc.setUrl(c.getUrl().replace("/ershouche/", b.getUrl()));
					mc.setSource(SOURCE_NAME);
					categories.add(mc);
				}
			}
			dao.saveBatch(categories);
		}
	}
	
	public void crawlModel() throws IOException{
		long l = modelRep.count();
		log4j.info("model: " + l);
		if(l == 0){
			List<GanjiModel> list = new ArrayList<>();
			List<PageSource> brands = getBrands();
			IUrlOverrider io = (String u) -> {
				return HOST + u;
			};
			for (PageSource b : brands) {
				b.setUrlOverrider(io);
				Document doc = Jsoup.parse(new URL(b.getUrl()), TIMEOUT);
				List<GanjiModel> models = getModels(doc, b);
				list.addAll(models);
			}
			dao.saveBatch(list);
		}
	}
	
	public void crawlCity() throws IOException{
		long l = cityRep.count();
		log4j.info("city: " + l);
		if(l == 0){
			List<GanjiCity> list = getCitiesOrProvinces();
			dao.saveBatch(list);
		}
	}
	
	public List<PageSource> getBrands() {
		List<PageSource> list = new ArrayList<>();
		InputStream in = null;
		try{
			URL url = new URL(BRAND_MODEL_API_URL);
			HttpURLConnection conn = (HttpURLConnection)url.openConnection();
			in = conn.getInputStream();
			String result = IOUtils.toString(in);
			JsonNode j = om.readTree(result);
			Iterator<Entry<String, JsonNode>> abc = j.fields();
			while(abc.hasNext()){
				Entry<String, JsonNode> entry = abc.next();
				Iterator<Entry<String, JsonNode>> brandIt = entry.getValue().fields();
				while(brandIt.hasNext()){
					Entry<String, JsonNode> brandEntry = brandIt.next();
					JsonNode n = brandEntry.getValue();
					PageSource brand = new PageSource();
					brand.setId(brandEntry.getKey());
					brand.setUrl(n.get("url").asText());
					brand.setName(n.get("title").asText());
					list.add(brand);
				}
			}
		}catch(Exception e){
			log4j.warn(e.getMessage(), e);
		}finally {
			IOUtils.closeQuietly(in);
		}
		return list;
	}
	
	private List<GanjiModel> getModels(Document doc, PageSource page) {
		//js-tag-show
		List<GanjiModel> list = new ArrayList<>();
		Elements eles = doc.select(".js-tag-show > dd > a");
		for (int i = 0; i < eles.size(); i++) {
			GanjiModel c = new GanjiModel();
			c.setUrl(eles.get(i).attr("href"));
			c.setModel(eles.get(i).text());
			c.setBrand(page.getName());
			if(!c.getModel().equals("不限") && !c.getModel().equals("更多")){
				list.add(c);
			}
			log4j.info(c);
		}
		return list;
	}
	
	private List<GanjiCity> getCitiesOrProvinces() throws IOException{
		Document doc = Jsoup.parse(new URL(CITY_START_URL), TIMEOUT);
		List<PageSource> provices = new ArrayList<>();
		Elements eles = doc.select(".seltion-cont > dl:nth-of-type(6) > dd > a");
		for (int i = 0; i < eles.size(); i++) {
			Element ele = eles.get(i);
			if("全国".equals(ele.text())){
				continue;
			}
			PageSource p = new PageSource();
			p.setId(ele.text());
			p.setName(p.getId());
			p.setUrl(ele.attr("abs:href"));
			log4j.info(p);
			provices.add(p);
		}
		
		List<GanjiCity> cities = new ArrayList<>();
		for (PageSource p : provices) {
			if(p.getUrl().startsWith(CITY_START_URL)){
				try {
					doc = Jsoup.parse(new URL(p.getUrl()), TIMEOUT);
					eles = doc.select(".subarea > a");
					for (int i = 0; i < eles.size(); i++) {
						GanjiCity c = new GanjiCity();
						Element ele = eles.get(i);
						c.setCity(ele.text());
						c.setUrl(ele.attr("abs:href"));
						c.setProvince(p.getName());
						cities.add(c);
						log4j.info(c);
					}
				} catch (IOException e) {
					log4j.error(e.getMessage(), e);
				}
				
			}else{
				GanjiCity c = new GanjiCity();
				c.setProvince(p.getName());
				c.setCity(p.getName());
				c.setUrl(p.getUrl());
				cities.add(c);
			}
		}
		return cities;
	}
}
